﻿/*
    Copyright 2010, 2011 Eric Wong
	contact@optimalcycling.com
	http://www.optimalcycling.com
	Optimal Cycling - Advanced power pacing program for cyclists
	
	This file is part of Optimal Cycling.

    Optimal Cycling is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Optimal Cycling is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Optimal Cycling.  If not, see <http://www.gnu.org/licenses/>.
*/

using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Data;
using System.Text;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using OptimalCycling.FileIO;
using OptimalCycling.FileIO.OC;
using CommandLineHelper;
using Cloo;

namespace OptimalCycling
{
    #region Alias

    using sizeT = System.UInt32;
    using size_array = System.UInt32;

    // Allow us to easily switch between floats and double values
#if REAL_IS_DOUBLE
    using real = System.Double;
    using real2 = Vector2d;
    using real4 = Vector4d;
    using real8 = Vector8d;
#else
    using real = System.Single;
    using real2 = Vector2;
    using real4 = Vector4;
    using real8 = Vector8;
#endif

    #endregion

    public partial class OCSolve
    {
        public class OCSolveData : IXmlSerializable
        {
            #region Constants

            const string NAME_powerValA = @"powerValA";
            const string NAME_powerValB = @"powerValB";

            const string NAME_fitness = @"fitness";
            const string NAME_correspondingIndex = @"correspondingIndex";

            const string NAME_solverVars = @"solverVars";
            const string NAME_randNumGenSeeds = @"randNumGenSeeds";

            const string NAME_bestFitIndex = @"bestFitIndex";
            const string NAME_bestTime = @"bestTime";

            const string NAME_bestPowerResults = @"bestPowerResults";
            const string NAME_bestKinematics = @"bestKinematics";

            const string NAME_convergeGenCount = @"convergeGenCount";
            const string NAME_convergePrevBest = @"convergePrevBest";

            const string NAME_integrityCheck = @"integrityCheck";

            #endregion

            #region Variables

            public real[] powerValA;
            public real[] powerValB;

            public real[] fitness;

            public sizeT[] correspondingIndex;
            public sizeT[] bestFitIndex;

            public SolverVars[] solverVars = new SolverVars[1];

            public uint4[] randNumGenSeeds;

            public real[] bestTime;

            public real8[] bestPowerResults;
            public real8[] bestKinematics;

            public real2[] fitStats = new real2[1];

            public ulong convergeGenCount;

            public int[] integrityCheck = { 1 };

            #endregion

            public XmlSchema GetSchema() { return null; }

            public void ReadXml(XmlReader reader)
            {
                reader.Read();
                
                XmlSerializeHelper.ReadRealArray(reader, out powerValA, NAME_powerValA);
                XmlSerializeHelper.ReadRealArray(reader, out powerValB, NAME_powerValB);

                XmlSerializeHelper.ReadRealArray(reader, out fitness, NAME_fitness);

                XmlSerializeHelper.ReadsizeTArray(reader, out correspondingIndex, NAME_correspondingIndex);

                #region solverVars
                reader.ReadStartElement(NAME_solverVars);

                solverVars[0] = new SolverVars();
                solverVars[0].currGen = ulong.Parse(reader.ReadElementString(@"currGen"));

                reader.MoveToContent();
                reader.ReadEndElement();
                #endregion

                XmlSerializeHelper.ReadUInt4Array(reader, out randNumGenSeeds, NAME_randNumGenSeeds);

                XmlSerializeHelper.ReadsizeTArray(reader, out bestFitIndex, NAME_bestFitIndex);
                XmlSerializeHelper.ReadRealArray(reader, out bestTime, NAME_bestTime);
                if (bestTime.Length != 1) throw new Exception("Intermediate Parsing: Incorrect number of bestTime elements.");

                XmlSerializeHelper.ReadReal8Array(reader, out bestPowerResults, NAME_bestPowerResults);
                XmlSerializeHelper.ReadReal8Array(reader, out bestKinematics, NAME_bestKinematics);

                XmlSerializeHelper.ReadULongValue(reader, out convergeGenCount, NAME_convergeGenCount);

                XmlSerializeHelper.ReadIntArray(reader, out integrityCheck, NAME_integrityCheck);

                if (integrityCheck == null || integrityCheck.Length != 1 || integrityCheck[0] != 1) 
                    throw new Exception("Intermediate xml integrity check failed.");
            }

            public void WriteXml(XmlWriter writer)
            {
                XmlSerializeHelper.WriteRealArray(writer, powerValA, NAME_powerValA);
                XmlSerializeHelper.WriteRealArray(writer, powerValB, NAME_powerValB);

                XmlSerializeHelper.WriteRealArray(writer, fitness, NAME_fitness);

                XmlSerializeHelper.WritesizeTArray(writer, correspondingIndex, NAME_correspondingIndex);

                #region solverVars
                writer.WriteStartElement(NAME_solverVars);
                writer.WriteElementString(@"currGen", (solverVars[0].currGen).ToString());
                writer.WriteEndElement();
                #endregion

                XmlSerializeHelper.WriteUInt4Array(writer, randNumGenSeeds, NAME_randNumGenSeeds);

                XmlSerializeHelper.WritesizeTArray(writer, bestFitIndex, NAME_bestFitIndex);
                XmlSerializeHelper.WriteRealArray(writer, bestTime, NAME_bestTime);

                XmlSerializeHelper.WriteReal8Array(writer, bestPowerResults, NAME_bestPowerResults);
                XmlSerializeHelper.WriteReal8Array(writer, bestKinematics, NAME_bestKinematics);

                XmlSerializeHelper.WriteULongValue(writer, convergeGenCount, NAME_convergeGenCount);

                XmlSerializeHelper.WriteIntArray(writer, integrityCheck, NAME_integrityCheck);
            }
        }

        #region Private Variables

        string folderPath;
        string intermedFolderPath;
        string intermedA, intermedB;
        string consoleOutFilePath;
        readonly TextWriter stdOutTextWriter = Console.Out;
        StreamWriter consoleOutFileWriter;

        ulong progressUpdateFrequency;
        ulong intermedSaveFrequency;
        bool keepAllIntermed;
        int intermedABFlag;

        int computePlatformIndex, computeDeviceIndex;
        int numGlobalExecutors;    // Multiple of 32 for Nvidia GPU, 64 for AMD GPU
        ComputeCommandQueue queue;
        ComputeKernel kernel;
        ComputeContext context;

        DataSet ocData;
        OCSolveData solveData;

        sizeT numSimPoints;
        sizeT numIndividuals;

        ComputeBuffer<real> powerValABuffer, powerValBBuffer;

        bool useNativeCPU;

        int powerAllSame;
        sizeT numIndividPerPop, numPopulations, dragCoeffWheelsNumPts;
        real coursePosLinearMax;

        ComputeBuffer<real> fitnessBuffer;
        ComputeBuffer<SolverVars> solverVarsBuffer;
        
        ComputeBuffer<sizeT> correspondingIndexBuffer;
        ComputeBuffer<sizeT> bestFitIndexBuffer;

        ComputeBuffer<uint4> randNumGenSeedsBuffer;

        ComputeBuffer<real> bestTimeBuffer;
        ComputeBuffer<real8> bestPowerResultsBuffer;
        ComputeBuffer<real8> bestKinematicsBuffer;
        ComputeBuffer<real2> fitStatsBuffer;

        #endregion

        public double Run(Arguments commandLine)
        {
            if (commandLine["About"] != null || commandLine["Version"] != null || commandLine["Ver"] != null)
            {
                DisplayInfo.DisplayVersion();
                return -1;
            }

            if (commandLine["Help"] != null || commandLine.Parameters.Count <= 0)
            {
                Console.WriteLine("-Version,\tSoftware version");
                Console.WriteLine("-Help,\t\tUsage info");
                Console.WriteLine();
                Console.WriteLine("-ComputeInfo,\tDisplay available compute platforms/devices");
                Console.WriteLine("-OutputCLLib,\tForce a recompile of the kernel. Use if upgrading drivers.");
                Console.WriteLine();
                Console.WriteLine("-Dir,\t\t[Req.] Specify directory with data");
                Console.WriteLine("-Template,\t[Opt.] Create template data in Dir and exits");
                Console.WriteLine("-Clean,\t\t[Opt.] Deletes the intermediate files in Dir and runs");
                Console.WriteLine("-CleanOnly,\t[Opt.] Deletes the intermediate files in Dir and exits");
                Console.WriteLine("-CPIndex,\t[Opt.] Specify which compute platform index to use. Default: 0");
                Console.WriteLine("-CDIndex,\t[Opt.] Specify which compute device index to use. Default: 0");
                Console.WriteLine("-NumExec,\t[Opt.] Number of \"threads\" to use. Default: 16 (CPU), 64 (GPU)");
                Console.WriteLine();
                Console.WriteLine("-UseNativeCPU,\tNot for general use. For demoing native Win32 build.");
                Console.WriteLine();
                Console.WriteLine();
                Console.WriteLine("***Examples***");
                Console.WriteLine("Find out what OpenCL hardware you have:\nOptimalCyclingCMD.exe -ComputeInfo");
                Console.WriteLine();
                Console.WriteLine("Run an optimization from the beginning:\nOptimalCyclingCMD.exe -Dir:\"C:\\User\\OptimalCycling_V_0_9_0\\Examples\\CCAP_1000m\" -clean");
                Console.WriteLine();
                Console.WriteLine("Omit -clean above to resume an interrupted optimization.");
                Console.WriteLine();
                Console.WriteLine("Specify which OpenCL device to use:\nOptimalCyclingCMD.exe -Dir:\"C:\\User\\OptimalCycling_V_0_9_0\\Examples\\CCAP_1000m\" -clean -CPIndex:0 -CDIndex:0 -NumExec: 16");
                Console.WriteLine();
                Console.WriteLine("Output an empty template for data:\nOptimalCyclingCMD.exe -Dir:\"C:\\User\\OptimalCycling_V_0_9_0\\Examples\\Empty\" -Template");
                Console.WriteLine();
                Console.WriteLine("See http://www.optimalcycling.com for more help/usage examples.");
                Console.WriteLine("Optimal Cycling is open source and cross platform.");
                Console.WriteLine("Note that you must have .Net/Mono + OpenCL drivers installed to run Optimal Cycling.");
                Console.WriteLine();

                Console.WriteLine();
                return -1;
            }

            #region ComputeInfo

            if (commandLine["ComputeInfo"] != null)
            {
                StringBuilder info = new StringBuilder();

                info.AppendLine("---[HOST INFO]---");
                info.AppendLine("Operating system: " + Environment.OSVersion);
                info.AppendLine("CLR version: " + Environment.Version);
                info.AppendLine("Processor count: " + Environment.ProcessorCount);

                info.AppendLine();
                info.AppendLine("---[OPENCL PLATFORMS]---");
                info.AppendLine();

                uint platformCounter = 0;
                foreach (ComputePlatform platform in ComputePlatform.Platforms)
                {
                    info.AppendLine("[PLATFORM INDEX] " + platformCounter);
                    info.AppendLine("Name: " + platform.Name);
                    info.AppendLine("Vendor: " + platform.Vendor);
                    info.AppendLine("Version: " + platform.Version);
                    info.AppendLine("Profile: " + platform.Profile);
                    info.AppendLine("Extensions:");

                    foreach (string extension in platform.Extensions)
                        info.AppendLine(" + " + extension);

                    info.AppendLine();

                    uint deviceCounter = 0;
                    foreach (ComputeDevice device in platform.Devices)
                    {
                        info.AppendLine("\t[DEVICE INDEX] " + deviceCounter);
                        info.AppendLine("\tName: " + device.Name);
                        info.AppendLine("\tVendor: " + device.Vendor);
                        info.AppendLine("\tDriver version: " + device.DriverVersion);
                        info.AppendLine("\tCompute units: " + device.MaxComputeUnits);
                        info.AppendLine("\tGlobal memory: " + device.GlobalMemorySize);
                        info.AppendLine("\tLocal memory: " + device.LocalMemorySize);
                        info.AppendLine("\tImage support: " + device.ImageSupport);
                        info.AppendLine("\tExtensions:");

                        foreach (string extension in device.Extensions)
                            info.AppendLine("\t + " + extension);

                        info.AppendLine();
                        deviceCounter++;
                    }
                    info.AppendLine();
                    platformCounter++;
                }
                Console.WriteLine(info.ToString());
                Console.WriteLine();
                return -1;
            }

            #endregion

            if (commandLine["Dir"] != null)
            {
                folderPath = commandLine["Dir"];
                Console.WriteLine("Data folder: " + folderPath);

                intermedFolderPath = Path.Combine(folderPath, MiscFiles.intermedFolderName);
                intermedA = Path.Combine(intermedFolderPath, MiscFiles.intermedA);
                intermedB = Path.Combine(intermedFolderPath, MiscFiles.intermedB);
                consoleOutFilePath = Path.Combine(folderPath, MiscFiles.consoleFileName);


                if (commandLine["Template"] != null)
                {
                    DataSet templateData;
                    FileOCDataV2_1.CreateBlankTemplate(out templateData, true);
                    FileOCDataV2_1.WriteOCData(templateData, false, folderPath);
                    Console.WriteLine("Template data created in data folder.");
                    return -1;
                }
                else if (commandLine["Clean"] != null || commandLine["CleanOnly"] != null)
                {
                    FileStream fs = new FileStream(consoleOutFilePath, FileMode.Create, FileAccess.Write, System.IO.FileShare.ReadWrite);
                    consoleOutFileWriter = new StreamWriter(fs);
                    consoleOutFileWriter.Write("");
                    consoleOutFileWriter.Flush();
                    consoleOutFileWriter.Close();
                    consoleOutFileWriter = null;

                    if (!Directory.Exists(intermedFolderPath))
                    {
                        Directory.CreateDirectory(intermedFolderPath);
                    }
                    else
                    {
                        string[] filesInIntermedFolder = Directory.GetFiles(intermedFolderPath);
                        for (int i = 0; i < filesInIntermedFolder.Length; i++)
                        {
                            File.Delete(filesInIntermedFolder[i]);
                        }
                    }

                    Console.WriteLine("Intermediates and console output file cleaned.");

                    if (commandLine["CleanOnly"] != null)
                        return -1;
                }

                if (commandLine["Resave"] != null)
                {
                    FileOCDataV2_1.ReadOCData(out ocData, folderPath);
                    FileOCDataV2_1.WriteOCData(ocData, false, folderPath);
                    Console.WriteLine("Files resaved and program exited.");
                    return -1;
                }

                if (commandLine["CPIndex"] != null)
                {
                    computePlatformIndex = Convert.ToInt32(commandLine["CPIndex"]);
                }
                else
                {
                    computePlatformIndex = 0;
                }
                Console.WriteLine("Compute Platform Index: " + computePlatformIndex + ", " + ComputePlatform.Platforms[computePlatformIndex].Name);

                if (commandLine["CDIndex"] != null)
                {
                    computeDeviceIndex = Convert.ToInt32(commandLine["CDIndex"]);
                }
                else
                {
                    computeDeviceIndex = 0;
                }
                Console.WriteLine("Compute Device Index: " + computeDeviceIndex + ", " + ComputePlatform.Platforms[computePlatformIndex].Devices[computeDeviceIndex].Name);

                if (commandLine["NumExec"] != null)
                {
                    numGlobalExecutors = Convert.ToInt32(commandLine["NumExec"]);
                }
                else
                {
                    switch (ComputePlatform.Platforms[computePlatformIndex].Devices[computeDeviceIndex].Type)
                    {
                        case ComputeDeviceTypes.Accelerator:
                            numGlobalExecutors = 64;
                            break;
                        case ComputeDeviceTypes.Gpu:
                            numGlobalExecutors = 64;
                            break;
                        case ComputeDeviceTypes.Cpu:
                            numGlobalExecutors = 16;
                            break;
                        default:
                            numGlobalExecutors = 16;
                            break;
                    }
                }
                Console.WriteLine("Number of Global Executors: " + numGlobalExecutors);

                Console.WriteLine();
            }
            else
            {
                Console.WriteLine("Error! Must specify a data folder.");
                Console.WriteLine();
                return -1;
            }

            if (commandLine["UseNativeCPU"] != null)
            {
                useNativeCPU = true;
                return Optimize();
            }
            else
            {
                useNativeCPU = false;
                bool outputCLLib = (commandLine["OutputCLLib"] != null ? true : false);
                bool outputCLSource = (commandLine["OutputCLSource"] != null ? true : false);
                bool outputCLBuildLog = (commandLine["OutputCLBuildLog"] != null ? true : false);
                CreateCL(outputCLLib, outputCLSource, outputCLBuildLog);
                return Optimize();
            }
        }

        private void ParseSolveData()
        {
            //__global real* powerValA
            //__global real* powerValB
            solveData.powerValA = new real[(size_array)numIndividuals * (size_array)numSimPoints];
            for (int i = 0; i < (int)numSimPoints; i++)
            {
                solveData.powerValA[i] = DataConversion.ConvertToReal(ocData.Tables[DataFiles.course.TableName].Rows[i][CourseColNames.power.ColName].ToString());
            }
            solveData.powerValB = (real[])solveData.powerValA.Clone();

            //__global real* fitness
            solveData.fitness = new real[(size_array)numIndividuals];

            // __global sizeT* correspondingIndex
            solveData.correspondingIndex = new sizeT[(size_array)numIndividuals];
            for (size_array i = 0; i < (size_array)numIndividuals; i++)
            {
                solveData.correspondingIndex[i] = (sizeT)(i * (size_array)numSimPoints);
            }

            // __global sizeT bestFitIndex
            solveData.bestFitIndex = new sizeT[1];
            solveData.bestFitIndex[0] = (sizeT)0;

            //__global SolverVars* solverVars
            // initialized in kernel
            solveData.solverVars[0] = new SolverVars();
            solveData.solverVars[0].currGen = 0;

            //__global uint4* randNumGenSeeds
            int rngSeed = Convert.ToInt32(ocData.Tables[DataFiles.optimizationParams.TableName].Rows[0][OptimizationParamsColNames.randNumGenSeeds.ColName]);
            Random rng = new Random(rngSeed);
            solveData.randNumGenSeeds = new uint4[(size_array)numIndividuals];
            for (int j = 0; j < solveData.randNumGenSeeds.Length; j++)
            {
                solveData.randNumGenSeeds[j].X = 0;
                solveData.randNumGenSeeds[j].Y = 0;
                solveData.randNumGenSeeds[j].Z = 0;
                solveData.randNumGenSeeds[j].W = 0;

                int numSum = 2;
                for (int k = 0; k < numSum; k++)
                    solveData.randNumGenSeeds[j].X += (uint)rng.Next(1, int.MaxValue);
                for (int k = 0; k < numSum; k++)
                    solveData.randNumGenSeeds[j].Y += (uint)rng.Next(1, int.MaxValue);
                for (int k = 0; k < numSum; k++)
                    solveData.randNumGenSeeds[j].Z += (uint)rng.Next(1, int.MaxValue);
                for (int k = 0; k < numSum; k++)
                    solveData.randNumGenSeeds[j].W += (uint)rng.Next(1, int.MaxValue);
            }

            #region Results

            //__global real* bestTime
            solveData.bestTime = new real[1];

            solveData.bestPowerResults = new real8[1];
            solveData.bestKinematics = new real8[(size_array)numSimPoints];

            //__global real2* fitStats
            solveData.fitStats = new real2[1];
            solveData.fitStats[0].X = 0;
            solveData.fitStats[0].Y = 0;

            #endregion
        }

        private void ReadKernelResults()
        {
            if (useNativeCPU)
                return;

            List<GCHandle> gcHandles = new List<GCHandle>();

            gcHandles.Add(GCHandle.Alloc(solveData.powerValA, GCHandleType.Pinned));
            queue.Read(powerValABuffer, false, 0, solveData.powerValA.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.powerValB, GCHandleType.Pinned));
            queue.Read(powerValBBuffer, false, 0, solveData.powerValB.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.fitness, GCHandleType.Pinned));
            queue.Read(fitnessBuffer, false, 0, solveData.fitness.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.correspondingIndex, GCHandleType.Pinned));
            queue.Read(correspondingIndexBuffer, false, 0, solveData.correspondingIndex.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.solverVars, GCHandleType.Pinned));
            queue.Read(solverVarsBuffer, false, 0, solveData.solverVars.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.randNumGenSeeds, GCHandleType.Pinned));
            queue.Read(randNumGenSeedsBuffer, false, 0, solveData.randNumGenSeeds.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.bestFitIndex, GCHandleType.Pinned));
            queue.Read(bestFitIndexBuffer, false, 0, solveData.bestFitIndex.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.bestTime, GCHandleType.Pinned));
            queue.Read(bestTimeBuffer, false, 0, solveData.bestTime.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.bestPowerResults, GCHandleType.Pinned));
            queue.Read(bestPowerResultsBuffer, false, 0, solveData.bestPowerResults.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.bestKinematics, GCHandleType.Pinned));
            queue.Read(bestKinematicsBuffer, false, 0, solveData.bestKinematics.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            gcHandles.Add(GCHandle.Alloc(solveData.fitStats, GCHandleType.Pinned));
            queue.Read(fitStatsBuffer, false, 0, solveData.fitStats.Length, gcHandles[gcHandles.Count - 1].AddrOfPinnedObject(), null);

            queue.Finish();
            for (int i = 0; i < gcHandles.Count; i++)
                gcHandles[i].Free();
        }

        private void ReadKernelResultsTime()
        {
            if (useNativeCPU)
                return;

            GCHandle bestTimeHandle = GCHandle.Alloc(solveData.bestTime, GCHandleType.Pinned);
            queue.Read(bestTimeBuffer, true, 0, solveData.bestTime.Length, bestTimeHandle.AddrOfPinnedObject(), null);
            bestTimeHandle.Free();

            GCHandle fitStatsHandle = GCHandle.Alloc(solveData.fitStats, GCHandleType.Pinned);
            queue.Read(fitStatsBuffer, false, 0, solveData.fitStats.Length, fitStatsHandle.AddrOfPinnedObject(), null);
            fitStatsHandle.Free();
        }

        private string CalculateMD5Hash(string input)
        {
            // step 1, calculate MD5 hash from input
            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
            byte[] hash = md5.ComputeHash(inputBytes);

            // step 2, convert byte array to hex string
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < hash.Length; i++)
            {
                sb.Append(hash[i].ToString("X2"));
            }
            return sb.ToString();
        }

        // outputCLLib must be true for the other flags to do anything
        private void CreateCL(bool outputCLLib, bool outputCLSource, bool outputCLBuildLog)
        {
            ComputeContextPropertyList cpl = new ComputeContextPropertyList(ComputePlatform.Platforms[computePlatformIndex]);
            ComputeDevice[] computeDeviceList = { ComputePlatform.Platforms[computePlatformIndex].Devices[computeDeviceIndex] };
            context = new ComputeContext(computeDeviceList, cpl, null, IntPtr.Zero);

            string deviceID = context.Platform.Name + context.Platform.Profile + context.Platform.Vendor + context.Platform.Version
                + context.Devices[0].DriverVersion + context.Devices[0].AddressBits 
                + context.Devices[0].Name + context.Devices[0].OpenCLCVersionString + context.Devices[0].VendorId
                + computePlatformIndex + computeDeviceIndex;
            string deviceIDHash = CalculateMD5Hash(deviceID);
            string deviceCLBinName = deviceIDHash + ".dll";

            ComputeProgram program;
            bool noCLLib = false;
            
            if (outputCLLib)
            {
                string clKernelSource = CLFiles.Source();

                if (outputCLSource)
                {
                    StreamWriter sw = new StreamWriter("CL Source All.cl");
                    sw.Write(clKernelSource);
                    sw.Close();
                }
                program = new ComputeProgram(context, new string[] { clKernelSource });
            }
            else
            {
                try
                {
                    Console.WriteLine("Reading the cache of the OpenCL binary for this device/program combination....");
                    List<byte[]> progBinaries = new List<byte[]>();
                    progBinaries.Add(File.ReadAllBytes(deviceCLBinName));
                    program = new ComputeProgram(context, progBinaries, context.Devices);
                }
                catch (Exception)
                {
                    Console.WriteLine("No cached OpenCL binary found for this device/program combination.");
                    Console.WriteLine("Compiling binary and caching it for future use.");
                    Console.WriteLine("This may take a few minutes....");
                    string clKernelSource = CLFiles.Source();
                    program = new ComputeProgram(context, new string[] { clKernelSource });
                    noCLLib = true;
                }
            }

            try
            {
                program.Build(computeDeviceList, null, null, IntPtr.Zero);

                if (outputCLLib || noCLLib)
                {
                    FileStream fs = new FileStream(deviceCLBinName, FileMode.Create, FileAccess.ReadWrite);
                    BinaryWriter bw = new BinaryWriter(fs);
                    bw.Write(program.Binaries[0]);
                    bw.Close();
                }
            }
            finally
            {
                if (outputCLBuildLog)
                {
                    string errorMessages = program.GetBuildLog(context.Devices[0]);
                    StreamWriter errorSW = new StreamWriter("BuildLog.txt");
                    errorSW.Write(errorMessages);
                    errorSW.Close();
                }
            }

            queue = new ComputeCommandQueue(context, context.Devices[0], ComputeCommandQueueFlags.None);

            kernel = program.CreateKernel("OCSimRun");
        }
    }
}